

#include <stdlib.h>
#include <stdarg.h>
#include "bdf_font.h"

void bf_Error(bf_t *bf, char *fmt, ...)
{
  va_list va;
  va_start(va, fmt);
  vprintf(fmt, va);
  printf("\n");
  va_end(va);
}

void bf_Log(bf_t *bf, char *fmt, ...)
{
  va_list va;
  va_start(va, fmt);
  if ( bf->is_verbose != 0 )
  {
    vprintf(fmt, va);
    printf("\n");
  }
  va_end(va);
}

/*
  bf_Open(0, BDF_BBX_MODE_MINIMAL)
*/
bf_t *bf_Open(int is_verbose, int bbx_mode)
{
  bf_t *bf;
  bf = (bf_t *)malloc(sizeof(bf_t));
  if ( bf != NULL )
  {
    bf->is_verbose = is_verbose;
    bf->glyph_list = NULL;
    bf->glyph_cnt = 0;
    bf->glyph_max = 0;
    bf->str_font = NULL;		/* argument for FONT in bdf file */
    bf->str_copyright = NULL;	/* argument for COPYRIGHT in bdf file */
    bf->target_data = NULL;
    bf->target_max = 0;
    bf->target_cnt = 0;
    bf->selected_glyphs = 0;
    
    bf->enc_w = 0;
    bf->enc_h = 0;
    bf->enc_x = 0;
    bf->enc_y = 0;
    
    bf->bbx_mode = bbx_mode;
    bf->tile_h_size = 1;
    bf->tile_v_size = 1;
        
    return bf;
  }
  return NULL;
}

void bf_Clear(bf_t *bf)
{
  int i;
  for( i = 0; i < bf->glyph_cnt; i++ )
  {
    free( bf->glyph_list[i] );
  }
  bf->glyph_cnt = 0;
  
  if ( bf->str_font != NULL )
    free(bf->str_font);
  bf->str_font = NULL;
  if ( bf->str_copyright != NULL )
    free(bf->str_copyright);
  bf->str_copyright = NULL;
}

void bf_Close(bf_t *bf)
{
  bf_Clear(bf);
  if ( bf->glyph_list != NULL )
    free(bf->glyph_list);
  if ( bf->target_data != NULL )
    free(bf->target_data);
  bf->glyph_list = NULL;
  bf->glyph_max = 0;
  free(bf);
}

static int bf_extend(bf_t *bf)
{
  int extend = 16;
  void *ptr;
  if ( bf->glyph_list == NULL )
  {
    ptr = malloc(extend*sizeof(bg_t *));
    bf->glyph_max = 0;
  }
  else
  {
    ptr = realloc(bf->glyph_list, (bf->glyph_max + extend)*sizeof(bg_t *));
  }
  if ( ptr == NULL )
    return 0;
  bf->glyph_max += extend;
  bf->glyph_list = (bg_t **)ptr;
  return 1;
}

/* returns glyph position or -1 */
int bf_AddGlyph(bf_t *bf)
{
    while( bf->glyph_max <= bf->glyph_cnt )
      if ( bf_extend(bf) == 0 )
	return -1;
    bf->glyph_list[bf->glyph_cnt] = bg_Open();
    if ( bf->glyph_list[bf->glyph_cnt] == NULL )
      return -1;
    bf->glyph_list[bf->glyph_cnt]->bf = bf;
    bf->glyph_cnt++;
    return bf->glyph_cnt-1;
}


static int bf_extend_target_data(bf_t *bf)
{
  int extend = 16;
  int i;
  void *ptr;
  if ( bf->target_data == NULL )
  {
    ptr = malloc(extend*sizeof(uint8_t));
    bf->target_max = 0;
  }
  else
  {
    ptr = realloc(bf->target_data, (bf->target_max + extend)*sizeof(uint8_t));
  }
  if ( ptr == NULL )
    return 0;
  bf->target_data = (uint8_t *)ptr;
  for( i = bf->target_max; i < bf->target_max + extend; i++ )
      bf->target_data[i] = 0;
  bf->target_max += extend;
  return 1;
}

int bf_AddTargetData(bf_t *bf, uint8_t data)
{
  while( bf->target_max <= bf->target_cnt )
    if ( bf_extend_target_data(bf) == 0 )
      return -1;
  bf->target_data[bf->target_cnt] = data;
  bf->target_cnt++;
  return bf->target_cnt-1;
}

void bf_ClearTargetData(bf_t *bf)
{
  int i;
  for( i = 0; i < bf->target_max; i++ )
    bf->target_data[i] = 0;
  bf->target_cnt = 0;
}

void bf_CalculateSelectedNumberOfGlyphs(bf_t *bf)
{
  int i;
  bg_t *bg;
  bf->selected_glyphs = 0;
  
  
  for( i = 0; i < bf->glyph_cnt; i++ )
  {
    bg = bf->glyph_list[i];
    if ( bg->map_to >= 0 )
    {
      bf->selected_glyphs++;
    }
  }
}


void bf_ReduceAllGlyph(bf_t *bf)
{
  int i;
  bg_t *bg;
  int red_x, red_y;
  
  bf_Log(bf, "Reduce: Start");
  
  for( i = 0; i < bf->glyph_cnt; i++ )
  {
    bg = bf->glyph_list[i];
    if ( bg->map_to >= 0 )
    {
      bg_ReduceGlyph(bg);
      //bg_ShowBitmap(bg, &(bg->bbx));
      red_x = bg->bitmap_width - bg->bbx.w;
      red_y = bg->bitmap_height - bg->bbx.h;
      if ( red_x > 0 || red_y > 0 )
      {
	//bf_Log(bf, "Reduce: Encoding %ld, x by %d, y by %d", bg->encoding, red_x, red_y);
      }
    }
  }
  bf_Log(bf, "Reduce: End");

}

void bf_ShowAllGlyphs(bf_t *bf, bbx_t *bbx)
{
  int i;
  bg_t *bg;
  for( i = 0; i < bf->glyph_cnt; i++ )
  {
    bg = bf->glyph_list[i];
    if ( bg->map_to >= 0 )
    {
      bg_ShowBitmap(bg, bbx); 
    }
  }
}

int bf_GetIndexByEncoding(bf_t *bf, long encoding)
{
  int i;
  bg_t *bg;
  for( i = 0; i < bf->glyph_cnt; i++ )
  {
    bg = bf->glyph_list[i];
    if ( bg->encoding == encoding )
      return i;
  }
  return -1;
}

void bf_CalculateMaxBBX(bf_t *bf)
{
  int i;
  int is_first = 1;
  int enc_idx;
  bg_t *bg;
  
  
  for( i = 0; i < bf->glyph_cnt; i++ )
  {
    bg = bf->glyph_list[i];
    if ( bg->map_to >= 0 )
    {
      if ( is_first != 0 )
      {
	bf->max = bg->bbx;
	bf->enc_x = bg->encoding;
	bf->enc_y = bg->encoding;
	bf->enc_w = bg->encoding;
	bf->enc_h = bg->encoding;
	is_first = 0;
      }
      else
      {
	enc_idx = bg_Max(bg, &(bf->max));
	switch(enc_idx)
	{
	  case 1:
	    bf->enc_w = bg->encoding;
	    break;
	  case 2:
	    bf->enc_h = bg->encoding;
	    break;
	  case 3:
	    bf->enc_x = bg->encoding;
	    break;
	  case 4:
	    bf->enc_y = bg->encoding;
	    break;
	}
      }
    }
  }
  
  if ( bf->bbx_mode == BDF_BBX_MODE_M8 )
  {
    /* old 
    bf->max.w = ( bf->max.w + 7 ) & ~7;
    bf->max.h = ( bf->max.h + 7 ) & ~7;
    */
    bf->max.w = 8;
    bf->max.h = 7;
  }

  
  bf_Log(bf, "CalculateMaxBBX: x=%ld, y=%ld, w=%ld, h=%ld", bf->max.x, bf->max.y, bf->max.w, bf->max.h);
  bf_Log(bf, "CalculateMaxBBX: Encodings x=%ld, y=%ld, w=%ld, h=%ld", bf->enc_x, bf->enc_y, bf->enc_w, bf->enc_h);
}

void bf_CalculateMinMaxDWidth(bf_t *bf)
{
  int i;
  bg_t *bg;
  
  bf->dx_min = 0x07fff;
  bf->dx_max = -0x07fff;
  
  bf->x_min = 0x07fff;
  bf->x_max = -0x07fff;
  
  for( i = 0; i < bf->glyph_cnt; i++ )
  {
    bg = bf->glyph_list[i];
    if ( bg->map_to >= 0 )
    {
      if ( bf->dx_min > bg->dwidth_x )
	bf->dx_min = bg->dwidth_x;
      if ( bf->dx_max < bg->dwidth_x )
	bf->dx_max = bg->dwidth_x;

      if ( bf->x_min > bg->bbx.x )
	bf->x_min = bg->bbx.x;
      if ( bf->x_max < bg->bbx.x )
	bf->x_max = bg->bbx.x;
      
    }
  }
  bf_Log(bf, "bf_CalculateMinMaxDWidth: dx_min=%ld, dx_max=%ld", bf->dx_min, bf->dx_max);
  bf_Log(bf, "bf_CalculateMinMaxDWidth: x_min=%ld, x_max=%ld", bf->x_min, bf->x_max);
  if ( bf->dx_min == bf->dx_max && bf->x_min >= 0 )
  {
    bf_Log(bf, "bf_CalculateMinMaxDWidth: Monospaced font.");	/* not sufficient: also bbx.x must be >= 0 for all glyphs */
    /* for a monospaced font, dx must be identical to bbx.w */
  }
}

int get_unsigned_bit_size(unsigned long v)
{
  int i = 0;
  while( v != 0 )
  {
    v = v / 2;
    i++;
  }
  return i;
}

int get_signed_bit_size(long v)
{
  if ( v < 0 )
    return get_unsigned_bit_size(-v-1) + 1;
  return get_unsigned_bit_size(v) + 1;
}

/*
#define BDF_BBX_MODE_MINIMAL 0
#define BDF_BBX_MODE_HEIGHT 1
#define BDF_BBX_MODE_MAX 2
#define BDF_BBX_MODE_M8 3
*/
void bf_copy_bbx_and_update_shift(bf_t *bf, bbx_t *target_bbx, bg_t *bg)
{
      /* modifing the following code requires update ind bdf_rle.c also */
      if ( bf->bbx_mode == BDF_BBX_MODE_MINIMAL )	// mode 0
      {
	*target_bbx = bg->bbx;	
      }
      else if ( bf->bbx_mode == BDF_BBX_MODE_MAX )	// mode 2 (monospace)
      {
	*target_bbx = bf->max;	
	target_bbx->x = 0;
	if ( bg->bbx.x < 0 )
	  bg->shift_x = bg->bbx.x;
	if ( target_bbx->w < bg->dwidth_x )
	  target_bbx->w = bg->dwidth_x;
      }
      else if ( bf->bbx_mode == BDF_BBX_MODE_M8 )
      {
	/* old
	target_bbx->w = bf->max.w;
	if ( target_bbx->w < bg->dwidth_x )
	  target_bbx->w = bg->dwidth_x;
	target_bbx->w = (target_bbx->w+7) & ~7;
	target_bbx->h = (bf->max.h+7) & ~7;
	*/
	target_bbx->w = 8;
	target_bbx->h = 8;
	target_bbx->x = bf->max.x;
	target_bbx->y = bf->max.y;
	target_bbx->x = 0;
	if ( bg->bbx.x < 0 )
	  bg->shift_x = bg->bbx.x;
      }
      else
      {

	*target_bbx = bf->max;
	target_bbx->w = bg->bbx.w;	
	target_bbx->x = bg->bbx.x;	
	target_bbx->x = 0;
	if ( bg->bbx.x < 0 )
	{
	  /* e.g. "j" */
	  target_bbx->w -= bg->bbx.x;
	  bg->shift_x = bg->bbx.x;
	}
	else
	{
	  /* e.g. "B" */
	  target_bbx->w += bg->bbx.x;
	  //bg->shift_x = bg->bbx.x;
	}
	if ( target_bbx->w < bg->dwidth_x )
	  target_bbx->w = bg->dwidth_x;
      }
  
  bg->width_deviation = target_bbx->w - bg->bbx.w;
}

void bf_CalculateMaxBitFieldSize(bf_t *bf)
{
  int i;
  bg_t *bg;
  int bs;
  bbx_t local_bbx;
  bf->bbx_x_max_bit_size = 0;
  bf->bbx_y_max_bit_size = 0;
  bf->bbx_w_max_bit_size = 0;
  bf->bbx_h_max_bit_size = 0;
  bf->dx_max_bit_size = 0;
  for( i = 0; i < bf->glyph_cnt; i++ )
  {
    bg = bf->glyph_list[i];
    if ( bg->map_to >= 0 )
    {
      
      bf_copy_bbx_and_update_shift(bf, &local_bbx, bg);
#ifdef OLD_CLODE      
      /* modifing the following code requires update ind bdf_rle.c also */
      if ( bf->bbx_mode == BDF_BBX_MODE_MINIMAL )
      {
	local_bbx = bg->bbx;	
      }
      else if ( bf->bbx_mode == BDF_BBX_MODE_MAX )
      {
	local_bbx = bf->max;	
	local_bbx.x = 0;
	if ( bg->bbx.x < 0 )
	  bg->shift_x = bg->bbx.x;
	if ( local_bbx.w < bg->dwidth_x )
	  local_bbx.w = bg->dwidth_x;
      }
      else if ( bf->bbx_mode == BDF_BBX_MODE_M8 )
      {
	local_bbx.w = bf->max.w;
	if ( local_bbx.w < bg->dwidth_x )
	  local_bbx.w = bg->dwidth_x;
	local_bbx.w = (local_bbx.w+7) & ~7;
	local_bbx.h = (bf->max.h+7) & ~7;
	local_bbx.x = bf->max.x;
	local_bbx.y = bf->max.y;
	local_bbx.x = 0;
	if ( bg->bbx.x < 0 )
	  bg->shift_x = bg->bbx.x;
      }
      else
      {

	local_bbx = bf->max;
	local_bbx.w = bg->bbx.w;	
	local_bbx.x = bg->bbx.x;	
	local_bbx.x = 0;
	if ( bg->bbx.x < 0 )
	{
	  /* e.g. "j" */
	  local_bbx.w -= bg->bbx.x;
	  bg->shift_x = bg->bbx.x;
	}
	else
	{
	  /* e.g. "B" */
	  local_bbx.w += bg->bbx.x;
	  //bg->shift_x = bg->bbx.x;
	}
	if ( local_bbx.w < bg->dwidth_x )
	  local_bbx.w = bg->dwidth_x;
      }
#endif
      bs = get_unsigned_bit_size(local_bbx.w);
      if ( bf->bbx_w_max_bit_size < bs )
	bf->bbx_w_max_bit_size = bs;
      
      bs = get_unsigned_bit_size(local_bbx.h);
      if ( bf->bbx_h_max_bit_size < bs )
	bf->bbx_h_max_bit_size = bs;
      
      //printf("%ld ", local_bbx.x);
      bs = get_signed_bit_size(local_bbx.x);
      if ( bf->bbx_x_max_bit_size < bs )
	bf->bbx_x_max_bit_size = bs;
      //printf("%d:%d ",(int)local_bbx->x, (int)bs);

      bs = get_signed_bit_size(local_bbx.y);
      if ( bf->bbx_y_max_bit_size < bs )
	bf->bbx_y_max_bit_size = bs;

      if ( bf->bbx_mode == BDF_BBX_MODE_MINIMAL )
      {
	bs = get_signed_bit_size(bg->dwidth_x);
      }
      else if ( bf->bbx_mode == BDF_BBX_MODE_MAX )
      {
	bs = get_signed_bit_size(local_bbx.w);
      }
      else
      {
	bs = get_signed_bit_size(local_bbx.w);
      }
      if ( bf->dx_max_bit_size < bs )
	bf->dx_max_bit_size = bs;
      
    }
  }
  bf_Log(bf, "bf_CalculateMaxBitFieldSize: bbx.x=%d, bbx.y=%d, bbx.w=%d, bbx.h=%d, dwidth=%d", 
      bf->bbx_x_max_bit_size, bf->bbx_y_max_bit_size, bf->bbx_w_max_bit_size, bf->bbx_h_max_bit_size, bf->dx_max_bit_size);
  
}

void bf_ShowMonospaceStatistics(bf_t *bf)
{
  int i;
  bg_t *bg;
  long cnt = 0;
  long max = 0;
  long sum = 0;
  for( i = 0; i < bf->glyph_cnt; i++ )
  {
    bg = bf->glyph_list[i];
    if ( bg->map_to >= 0 )
    {
      if ( max < bg->width_deviation )
	max = bg->width_deviation;
      sum += bg->width_deviation;
      cnt++;
    }
  }
  if ( cnt == 0 )
    cnt++;
  bf_Log(bf, "Monospace Statistics: Max width extention %ld, average width extention %ld.%ld", max, sum/cnt, (sum*10/cnt) % 10 );
  /*
    Monospaced font: average width extention does not differ much between -b 1 and --b 2
    Variable width font: big difference for the average width extention between modes 1 and 2.
  
    Examples:
  
      ./bdfconv -b 1 -v ../bdf/profont12.bdf
	Monospace Statistics: Max width extention 6, average width extention 1.7
      ./bdfconv -b 2 -v ../bdf/profont12.bdf
	Monospace Statistics: Max width extention 6, average width extention 1.7
      --> profont12.bdf is a monospaced font  
  
      ./bdfconv -b 1 -v ../bdf/helvR12.bdf
	Monospace Statistics: Max width extention 6, average width extention 1.9
      ./bdfconv -b 2 -v ../bdf/helvR12.bdf
	Monospace Statistics: Max width extention 15, average width extention 8.0
      --> helvR12.bdf is not a monospaced font  
  
  */
}


int bf_WriteUCGCByFP(bf_t *bf, FILE *out_fp, const char *fontname, const char *indent)
{
  int i;
  int bytes_per_line = 16;
  
  fprintf(out_fp, "/*\n");
  fprintf(out_fp, "  Fontname: %s\n", bf->str_font);
  fprintf(out_fp, "  Copyright: %s\n", bf->str_copyright);
  fprintf(out_fp, "  Glyphs: %d/%d\n", (int)bf->selected_glyphs, (int)bf->glyph_cnt );
  fprintf(out_fp, "  BBX Build Mode: %d\n", (int)bf->bbx_mode);
  fprintf(out_fp, "*/\n");
  fprintf(out_fp, "#include \"ucg.h\"\n");  
  fprintf(out_fp, "const ucg_fntpgm_uint8_t %s[%d] UCG_FONT_SECTION(\"%s\") = {\n", fontname, bf->target_cnt, fontname);
  fprintf(out_fp, "  ");
    
  for( i = 0; i < bf->target_cnt; i++ )
  {
    fprintf(out_fp, "%d", bf->target_data[i]);
    if ( i+1 != bf->target_cnt )
      fprintf(out_fp, ",");
    if ( (i+1) % bytes_per_line == 0 )
      fprintf(out_fp, "\n%s", indent);    
  }

  fprintf(out_fp, "};\n");  
  return 1;
}

int bf_WriteU8G2CByFP(bf_t *bf, FILE *out_fp, const char *fontname, const char *indent)
{
  int i;
  int bytes_per_line = 32;
  int extra1;
  
  fprintf(out_fp, "/*\n");
  fprintf(out_fp, "  Fontname: %s\n", bf->str_font);
  fprintf(out_fp, "  Copyright: %s\n", bf->str_copyright);
  fprintf(out_fp, "  Glyphs: %d/%d\n", (int)bf->selected_glyphs, (int)bf->glyph_cnt );
  fprintf(out_fp, "  BBX Build Mode: %d\n", (int)bf->bbx_mode);
  fprintf(out_fp, "*/\n");

  if ( bf->target_data[bf->target_cnt-1] == 0 )
    extra1 = 0;
  else
    extra1 = 1;
  
  if ( bf->target_cnt-1+extra1 > 32760 )
  {
    fprintf(out_fp, "#ifdef U8G2_USE_LARGE_FONTS\n");    
  }
  
  if ( bf->bbx_mode == 3 )	// maybe better check for the font_format
  {
    //fprintf(out_fp, "#include \"u8x8.h\"\n");  
    fprintf(out_fp, "const uint8_t %s[%d] U8X8_FONT_SECTION(\"%s\") = \n", fontname, bf->target_cnt+extra1, fontname);
  }
  else
  {
    //fprintf(out_fp, "#include \"u8g2.h\"\n");  
    fprintf(out_fp, "const uint8_t %s[%d] U8G2_FONT_SECTION(\"%s\") = \n", fontname, bf->target_cnt+extra1, fontname);
  }
  fprintf(out_fp, "%s\"", indent);
    
  for( i = 0; i < bf->target_cnt-1+extra1; i++ )
  {
    if ( bf->target_data[i] < 32 || bf->target_data[i] == '\"' || bf->target_data[i] == '\\'  || bf->target_data[i] == '?' || ( bf->target_data[i] >= '0' && bf->target_data[i] <= '9' ))
    {
      fprintf(out_fp, "\\%o", bf->target_data[i]);
      //fprintf(out_fp, "\\x%02x", bf->target_data[i]);
    }
    else if ( bf->target_data[i] < 127 )		/* issue 482, do not output ASCII char 127, instead use octal code for 127 */
    {
      fprintf(out_fp, "%c", bf->target_data[i]);
    }
    else
    {
      fprintf(out_fp, "\\%o", bf->target_data[i]);
    }
    if ( (i+1) % bytes_per_line == 0 )
      fprintf(out_fp, "\"\n%s\"", indent); 
  }

  fprintf(out_fp, "\";\n");  

  if ( bf->target_cnt-1+extra1 > 32760 )
  {
    fprintf(out_fp, "#endif /* U8G2_USE_LARGE_FONTS */\n");    
  }
  
  return 1;
}

int bf_WriteUCGCByFilename(bf_t *bf, const char *filename, const char *fontname, const char *indent)
{
  FILE *fp;
  fp = fopen(filename, "wb");
  if ( fp == NULL )
  {
    bf_Log(bf, "bf_WriteUCGCByFilename: Open error '%s'", filename);
    return 0;
  }
  
  bf_WriteUCGCByFP(bf, fp, fontname, indent);
  bf_Log(bf, "bf_WriteUCGCByFilename: Write file '%s'", filename);
  
  fclose(fp);
  return 1;
}

/* called from main() */
int bf_WriteU8G2CByFilename(bf_t *bf, const char *filename, const char *fontname, const char *indent)
{
  FILE *fp;
  fp = fopen(filename, "wb");
  if ( fp == NULL )
  {
    bf_Log(bf, "bf_WriteU8G2CByFilename: Open error '%s'", filename);
    return 0;
  }
  
  bf_WriteU8G2CByFP(bf, fp, fontname, indent);
  bf_Log(bf, "bf_WriteU8G2CByFilename: Write file '%s'", filename);
  
  fclose(fp);
  return 1;
}


/*
  xo, yo: offset for 8x8 fonts (font_format==2)
  called from main()
*/
bf_t *bf_OpenFromFile(const char *bdf_filename, int is_verbose, int bbx_mode, const char *map_str, const char *map_file_name, int font_format, int xo, int yo, int th, int tv)
{
  bf_t *bf;

  bf = bf_Open(is_verbose, bbx_mode);
  if ( bf != NULL )
  {
    bf->tile_h_size = th;
    bf->tile_v_size = tv;
    
    if ( bf_ParseFile(bf, bdf_filename) != 0 )
    {
      if ( map_file_name[0] != '\0' ) 
      {
	bf_MapFile(bf, map_file_name);
      }
      else
      {
	bf_Map(bf, map_str);
      }
      bf_CalculateSelectedNumberOfGlyphs(bf);
      
      bf_ReduceAllGlyph(bf);
      bf_CalculateMaxBBX(bf);
      //bf_ShowAllGlyphs(bf, &(bf->max));
      bf_CalculateMinMaxDWidth(bf);
      
      /* issue 669 */
      if ( bf->bbx_mode == BDF_BBX_MODE_MAX )
	if ( bf->max.w < bf->dx_max )
	  bf->max.w = bf->dx_max;
      
      bf_CalculateMaxBitFieldSize(bf);  
      
      
      if ( font_format == 0 || font_format == 1 )
      {
	bf_RLECompressAllGlyphs(bf);
      }
      else
      {
	bf_Generate8x8Font(bf, xo, yo);	/* bdf_8x8.c */
      }
      
      if ( bf->bbx_mode != BDF_BBX_MODE_MINIMAL )
	bf_ShowMonospaceStatistics(bf);	/* Show stats only for none minimal mode. For minimal mode it will always be zero */
    
      return bf;
    }
    bf_Close(bf);
  }
  return NULL;
}

